using SS.Common.Collection;
using SS.Common.Logging;

namespace SS.Common.Threading.JobExecutor
{
    using System;
    using System.Collections.Generic;
    using System.Threading;

    /// <summary>
    /// Define a way to execute job by putting job in queue
    /// </summary>
    public interface IJobExecutor
    {
        /// <summary>
        /// Waiting for pool to idle
        /// </summary>
        void WaitForIdle();
    }

    /// <summary>
    /// Implement IJobexecutor for IJob type
    /// Support running IJob in a thread pool
    /// Note Exception from IJob.Execute function will be ignored, assumed that jobs handle exception themselves
    /// Note Currently do not reject job if shutting down
    /// Note No upper limit for queue size
    /// </summary>
    /// <typeparam name="T">Type of job
    /// </typeparam>
    public class JobExecutor<T> : IJobExecutor<T>, IJobExecutor
        where T : IJob
    {
        /// <summary>
        /// Logger for logging
        /// </summary>
        private static readonly ILogger Logger = LoggerManager.GetLogger(typeof(JobExecutor<T>).Name);

        #region General variables

        /// <summary>
        /// Synchronize jobs queue
        /// </summary>
        private readonly SyncQueue<T> queue;

        /// <summary> 
        /// Queue size check interval
        /// </summary> 
        private int intervalQueueSizeCheck = 0;

        /// <summary>
        /// List of worker threads
        /// </summary>
        private readonly List<Thread> workerThreads;

        /// <summary>
        /// Number of worker threads
        /// </summary>
        private int numThreads = 5;

        #endregion

        #region Thread control variables

        /// <summary>
        /// Thread execution flag
        /// While this flag is false, threads run
        /// </summary>
        private bool stopAll = true;

        /// <summary>
        /// Idle event
        /// Signal when pool is idle
        /// </summary>
        private readonly ManualResetEvent isIdleHandle;

        /// <summary>
        /// Number of remain work items
        /// </summary>
        private int numWorkItems;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="JobExecutor{T}"/> class
        /// With 5 woker threads
        /// </summary>
        public JobExecutor()
            : this(5)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="JobExecutor{T}"/> class
        /// With specified number of worker threads
        /// </summary>
        /// <param name="numThreads">Number of worker threads</param>
        public JobExecutor(int numThreads)
        {
            // Jobs queue
            this.queue = new SyncQueue<T>();

            // List of workers thread
            this.workerThreads = new List<Thread>();

            // Number of the worker thread
            this.NumThreads = numThreads;

            // Init idle event
            this.isIdleHandle = new ManualResetEvent(true);
        }

        #endregion

        #region properties

        /// <summary>
        /// Gets or sets number of thread used
        /// </summary>
        /// <value>
        /// The number of thread used
        /// </value>
        public int NumThreads
        {
            get
            {
                return this.numThreads;
            }

            set
            {
                this.numThreads = value < 1 ? 1 : value;
            }
        }

        #endregion

        #region IJobExecutor<T> Members

        /// <summary>
        /// Initialize ThreadPool
        ///  - Init members
        ///  - Start worker threads
        /// </summary>
        public void Initialize()
        {
            lock (this.queue.LockRoot)
            {
                // If initialized -> do nothing
                if (this.stopAll == false)
                {
                    return;
                }

                // Update environment
                this.workerThreads.Clear();
                this.queue.Clear();

                // Reset stop flag
                this.stopAll = false;

                // Reset work items
                this.numWorkItems = 0;

                // Signal to idle
                this.isIdleHandle.Set();

                // Create and start worker threads
                for (var i = 0; i < this.numThreads; i++)
                {
                    // Create worker thread
                    var t = new Thread(this.ProcessQueuedJobs) { IsBackground = true };

                    // Add to worker list
                    this.workerThreads.Add(t);

                    // Start thread
                    t.Start();
                }
            }
        }

        /// <summary>
        /// Enqueue a job
        /// </summary>
        /// <param name="job">Job object to enqueue</param>
        public void Queue(T job)
        {
            // Increase number of work items
            this.IncrementWorkItemsCount();

            // Enqueue job
            this.queue.Enqueue(job);
        }

        /// <summary>
        /// Stop all worker threads and clear queued jobs
        /// </summary>
        /// <param name="force">
        /// Force stop worker threads if true
        /// </param>
        /// <returns>
        /// An empty list as currently do not check for remain tasks
        /// </returns>
        public List<T> Stop(bool force)
        {
            lock (this.queue.LockRoot)
            {
                // Set stop flag to true
                this.stopAll = true;

                // Update number of remaining work items
                this.numWorkItems -= this.queue.Count;

                // Clear jobs queue
                this.queue.Clear();
            }

            // Abort worker threads if force
            if (force)
            {
                foreach (var t in this.workerThreads)
                {
                    if (t != null && t.IsAlive)
                    {
                        try
                        {
                            t.Abort();
                        }
                        catch (ThreadAbortException)
                        {
                        }
                        catch (ThreadStateException)
                        {
                        }
                        catch (ThreadInterruptedException)
                        {
                        }
                    }
                }

                // Signal to idle
                this.isIdleHandle.Set();
            }

            return new List<T>();
        }

        /// <summary>
        /// Wait for the executor terminated
        /// Currently not implemented (Do nothing)
        /// </summary>
        /// <param name="millisecondsTimeout">
        /// Timeout in milliseconds
        /// </param>
        /// <returns>
        /// false as no implementation
        /// </returns>
        public bool WaitForTermination(int millisecondsTimeout)
        {
            return false;
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Check interface <see cref="IJobExecutor"/>
        /// </summary>
        public void Dispose()
        {
            // Force stop
            this.Stop(true);
        }

        #endregion

        #region Thread function

        /// <summary>
        /// Workder thread function
        /// Used to execute queued jobs
        /// </summary>
        private void ProcessQueuedJobs()
        {
            while (!this.stopAll)
            {
                // Dequeue job
                var job = this.queue.Dequeue();
                
                // Validate job)
                if (Equals(job, default(T)))
                {
                    continue;
                }

                // Execute job
                try
                {
                    job.Execute();
                }
                catch (Exception ex)
                {
                    Logger.ErrorFormat("Process Job error: {0}", ex);
                }

                // Decrease number of work items
                this.DecrementWorkItemsCount();

                // 1 more job processed by JobExecutor
                var count = Interlocked.Increment(ref this.intervalQueueSizeCheck);
                if (count >= 2000)
                {
                    lock (this)
                    {
                        Logger.InfoFormat("JobExecutor Queue Size: {0}", this.queue.Count);
                        this.intervalQueueSizeCheck = 0;
                    }
                }
            }
        }

        #endregion

        /// <summary>
        /// Waiting for pool to idle
        /// </summary>
        public void WaitForIdle()
        {
            // Wait until idle event is signaled
            this.isIdleHandle.WaitOne();
        }

        /// <summary>
        /// Increase the number of work items
        /// </summary>
        private void IncrementWorkItemsCount()
        {
            var count = Interlocked.Increment(ref this.numWorkItems);

            // If there are remaining work items -> set to not idle
            if (count == 1)
            {
                this.isIdleHandle.Reset();
            }
        }

        /// <summary>
        /// Decrease the number of work items
        /// </summary>
        private void DecrementWorkItemsCount()
        {
            var count = Interlocked.Decrement(ref this.numWorkItems);

            // If no remaining work item -> signal to idle
            if (count == 0)
            {
                this.isIdleHandle.Set();
            }
        }
    }
}
